Option Explicit

' TimeGPS indexes.
Private Const YearIndex   As Byte = 1
Private Const MonthIndex  As Byte = 2
Private Const DayIndex    As Byte = 3
Private Const HourIndex   As Byte = 4
Private Const MinuteIndex As Byte = 5
Private Const SecondIndex As Byte = 6

' FixGPS indexes.
Private Const LonDegIndex As Byte = 1
Private Const LonMinIndex As Byte = 2
Private Const LatDegIndex As Byte = 3
Private Const LatMinIndex As Byte = 4
Private Const AltIndex    As Byte = 5
Private Const VelEIndex   As Byte = 6
Private Const VelNIndex   As Byte = 7
Private Const VelUIndex   As Byte = 8

Private PositionStatus As Byte
Private EPH As Integer

Private Const ChrPlus  As Byte = 43   ' +
Private Const ChrEast  As Byte = 69   ' E
Private Const ChrNorth As Byte = 78   ' N
Private Const ChrUp    As Byte = 85   ' U

Private Const MaxDigits As Byte = 5
Private PowTenLookup(1 To MaxDigits) As Integer

Private DataIsValid As Boolean
'-------------------------------------------------------------------------------
Public Sub InitializeGPS()

    PowTenLookup(1) = 1
    PowTenLookup(2) = 10
    PowTenLookup(3) = 100
    PowTenLookup(4) = 1000
    PowTenLookup(5) = 10000

    Call OpenSerialPortGPS

End Sub
'-------------------------------------------------------------------------------
Public Sub GetFixGPS( _
    ByRef TimeGPS() As Byte, _
    ByRef Position() As Single, _
    ByRef Velocity() As Single, _
    ByRef TimeIsValid As Boolean, _
    ByRef PositionIsValid As Boolean, _
    ByRef VelocityIsValid As Boolean)

    Const SentenceStart As Byte = 64   ' "@"

    Dim N As Byte
    Dim Temp As Byte
    Dim Sign As Integer
    Dim FixGPS(LonDegIndex To VelUIndex) As Integer

    '---------------
    ' Start sentence                             ' Character
    '---------------                             ' Position

    Do Until (GetByte = SentenceStart)           ' 1
        ' Null
    Loop

    '-----
    ' Time
    '-----

    DataIsValid = True
    TimeIsValid = True

    For N = 1 To 6
        TimeGPS(N) = CByte(GetNumber(2))         ' 2 .. 13
    Next

    ' Error check.
    If (Not DataIsValid) Then
        TimeIsValid = False
    End If

    '---------
    ' Position
    '---------

    DataIsValid = True
    PositionIsValid = True

    ' Latitude.
    Sign = GetSign(ChrNorth)                     ' 14
    FixGPS(LatDegIndex) = GetNumber(2) * Sign    ' 15 .. 16
    FixGPS(LatMinIndex) = GetNumber(5)           ' 17 .. 21

    ' Longitude
    Sign = GetSign(ChrEast)                      ' 22
    FixGPS(LonDegIndex) = GetNumber(3) * Sign    ' 23 .. 25
    FixGPS(LonMinIndex) = GetNumber(5)           ' 26 .. 30

    PositionStatus = GetByte                     ' 31

    EPH = GetNumber(3)                           ' 32 .. 34

    ' Altitude.
    Sign = GetSign(ChrPlus)                      ' 35
    FixGPS(AltIndex) = GetNumber(5) * Sign       ' 36 .. 40

    ' Error check.
    If (Not DataIsValid) Then
        PositionIsValid = False
    End If

    '---------
    ' Velocity
    '---------

    DataIsValid = True
    VelocityIsValid = True

    ' Velocity east/west.
    Sign = GetSign(ChrEast)                      ' 41
    FixGPS(VelEIndex) = GetNumber(4) * Sign      ' 42 .. 45

    ' Velocity north/south.
    Sign = GetSign(ChrNorth)                     ' 46
    FixGPS(VelNIndex) = GetNumber(4) * Sign      ' 47 .. 50

    ' Velocity up/down.
    Sign = GetSign(ChrUp)                        ' 51
    FixGPS(VelUIndex) = GetNumber(4) * Sign      ' 52 .. 55

    ' Error check.
    If (Not DataIsValid) Then
        VelocityIsValid = False
    End If

    '-------------
    ' End sentence
    '-------------

    Temp = GetByte                               ' 56
    Temp = GetByte                               ' 57

    '-----------------
    ' Unit conversions
    '-----------------

    Position(1) = ToDegrees(FixGPS(LonDegIndex), FixGPS(LonMinIndex))
    Position(2) = ToDegrees(FixGPS(LatDegIndex), FixGPS(LatMinIndex))
    Position(3) = CSng(FixGPS(AltIndex))

    Velocity(1) = CSng(FixGPS(VelEIndex)) / 10.0
    Velocity(2) = CSng(FixGPS(VelNIndex)) / 10.0
    Velocity(3) = CSng(FixGPS(VelUIndex)) / 100.0

End Sub
'-------------------------------------------------------------------------------
Public Function GetNumber( _
    ByVal DigitCount As Byte) As Integer

' This function converts a decimal string to an integer. The string is of
' length DigitCount.
'
' Each character must be a decimal digit -- otherwise the character is
' illegal. If any illegal characters are found, they're treated as
' equivalent to "0" and the error flag DataIsValid is set to False.

    Dim PowTen As Integer
    Dim N As Byte
    Dim InByte As Byte

    PowTen = PowTenLookup(DigitCount)
    GetNumber = 0

    For N = 1 To DigitCount

        InByte = GetByte

        ' Check for legal decimal digit.
        If (InByte >= 48) And (InByte <= 57) Then
            GetNumber = GetNumber + (CInt(InByte - 48) * PowTen)
        Else
            DataIsValid = False
        End If

        PowTen = PowTen \ 10
    Next

End Function
'-------------------------------------------------------------------------------
Public Function GetSign( _
    ByVal Value As Byte) As Integer

' This function reads a byte and returns a sign value that depends on the
' specified number Value. If the new byte matches, the sign is positive.
' Otherwise the sign is negative.

    If (GetByte = Value) Then
        GetSign = 1
    Else
        GetSign = -1
    End If

End Function
'-------------------------------------------------------------------------------
Public Function GetByte() As Byte

' This function reads the next byte from the input device. If the byte matches
' InvalidTag, the flag DataIsValid is set to False.

    Const InvalidTag As Byte = 95  ' "_"

    GetByte = GetByteGPS

    If (GetByte = InvalidTag) Then
        DataIsValid = False
    End If

End Function
'-------------------------------------------------------------------------------
Private Function ToDegrees( _
    ByVal Degrees As Integer, _
    ByVal mMinutes As Integer) As Single

' Converts integer degrees/minutes to float degrees. Note that mMinutes are
' in units of 10^3 minutes.

    Dim Deg As Single
    Dim Frac As Single

    Deg = CSng(Degrees)

    If (Deg >= 0.0) Then
        Frac = CSng(mMinutes)
    Else
        Frac = -CSng(mMinutes)
    End If

    ' Convert to fractional degrees.
    Frac = Frac / 60000.0

    ToDegrees = Deg + Frac

End Function
'-------------------------------------------------------------------------------
